using System;
using System.IO;
using System.Collections.Generic;
using System.Security.Cryptography;
using System.Threading;

namespace PickGold.Zip
{
#if AESCRYPTO
	/// <summary>
	///   This is a helper class supporting WinZip AES encryption.
	///   This class is intended for use only by the DotNetZip library.
	/// </summary>
	///
	/// <remarks>
	///   Most uses of the DotNetZip library will not involve direct calls into
	///   the WinZipAesCrypto class.  Instead, the WinZipAesCrypto class is
	///   instantiated and used by the ZipEntry() class when WinZip AES
	///   encryption or decryption on an entry is employed.
	/// </remarks>
	internal class WinZipAesCrypto
	{
		internal byte[] _Salt;
		internal byte[] _providedPv;
		internal byte[] _generatedPv;
		internal int _KeyStrengthInBits;
		private byte[] _MacInitializationVector;
		private byte[] _StoredMac;
		private byte[] _keyBytes;
		private Int16 PasswordVerificationStored;
		private Int16 PasswordVerificationGenerated;
		private int Rfc2898KeygenIterations = 1000;
		private string _Password;
		private bool _cryptoGenerated;

		private WinZipAesCrypto(string password, int KeyStrengthInBits)
		{
			_Password = password;
			_KeyStrengthInBits = KeyStrengthInBits;
		}

		public static WinZipAesCrypto Generate(string password, int KeyStrengthInBits)
		{
			WinZipAesCrypto c = new WinZipAesCrypto(password, KeyStrengthInBits);

			int saltSizeInBytes = c._KeyStrengthInBytes / 2;
			c._Salt = new byte[saltSizeInBytes];
			Random rnd = new Random();
			rnd.NextBytes(c._Salt);
			return c;
		}



		public static WinZipAesCrypto ReadFromStream(string password, int KeyStrengthInBits, Stream s)
		{
			// from http://www.winzip.com/aes_info.htm
			//
			// Size(bytes)   Content
			// -----------------------------------
			// Variable      Salt value
			// 2             Password verification value
			// Variable      Encrypted file data
			// 10            Authentication code
			//
			// ZipEntry.CompressedSize represents the size of all of those elements.

			// salt size varies with key length:
			//    128 bit key => 8 bytes salt
			//    192 bits => 12 bytes salt
			//    256 bits => 16 bytes salt

			WinZipAesCrypto c = new WinZipAesCrypto(password, KeyStrengthInBits);

			int saltSizeInBytes = c._KeyStrengthInBytes / 2;
			c._Salt = new byte[saltSizeInBytes];
			c._providedPv = new byte[2];

			s.Read(c._Salt, 0, c._Salt.Length);
			s.Read(c._providedPv, 0, c._providedPv.Length);

			c.PasswordVerificationStored = (Int16)(c._providedPv[0] + c._providedPv[1] * 256);
			if (password != null)
			{
				c.PasswordVerificationGenerated = (Int16)(c.GeneratedPV[0] + c.GeneratedPV[1] * 256);
				if (c.PasswordVerificationGenerated != c.PasswordVerificationStored)
					throw new BadPasswordException("bad password");
			}

			return c;
		}

		public byte[] GeneratedPV
		{
			get
			{
				if (!_cryptoGenerated) _GenerateCryptoBytes();
				return _generatedPv;
			}
		}


		public byte[] Salt
		{
			get
			{
				return _Salt;
			}
		}


		private int _KeyStrengthInBytes
		{
			get
			{
				return _KeyStrengthInBits / 8;

			}
		}

		public int SizeOfEncryptionMetadata
		{
			get
			{
				// 10 bytes after, (n-10) before the compressed data
				return _KeyStrengthInBytes / 2 + 10 + 2;
			}
		}

		public string Password
		{
			set
			{
				_Password = value;
				if (_Password != null)
				{
					PasswordVerificationGenerated = (Int16)(GeneratedPV[0] + GeneratedPV[1] * 256);
					if (PasswordVerificationGenerated != PasswordVerificationStored)
						throw new PickGold.Zip.BadPasswordException();
				}
			}
			private get
			{
				return _Password;
			}
		}


		private void _GenerateCryptoBytes()
		{
			//Console.WriteLine(" provided password: '{0}'", _Password);

			System.Security.Cryptography.Rfc2898DeriveBytes rfc2898 =
				new System.Security.Cryptography.Rfc2898DeriveBytes(_Password, Salt, Rfc2898KeygenIterations);

			_keyBytes = rfc2898.GetBytes(_KeyStrengthInBytes); // 16 or 24 or 32 ???
			_MacInitializationVector = rfc2898.GetBytes(_KeyStrengthInBytes);
			_generatedPv = rfc2898.GetBytes(2);

			_cryptoGenerated = true;
		}


		public byte[] KeyBytes
		{
			get
			{
				if (!_cryptoGenerated) _GenerateCryptoBytes();
				return _keyBytes;
			}
		}


		public byte[] MacIv
		{
			get
			{
				if (!_cryptoGenerated) _GenerateCryptoBytes();
				return _MacInitializationVector;
			}
		}

		public byte[] CalculatedMac;


		public void ReadAndVerifyMac(System.IO.Stream s)
		{
			bool invalid = false;

			// read integrityCheckVector.
			// caller must ensure that the file pointer is in the right spot!
			_StoredMac = new byte[10];  // aka "authentication code"
			s.Read(_StoredMac, 0, _StoredMac.Length);

			if (_StoredMac.Length != CalculatedMac.Length)
				invalid = true;

			if (!invalid)
			{
				for (int i = 0; i < _StoredMac.Length; i++)
				{
					if (_StoredMac[i] != CalculatedMac[i])
						invalid = true;
				}
			}

			if (invalid)
				throw new PickGold.Zip.BadStateException("The MAC does not match.");
		}

	}


	#region DONT_COMPILE_BUT_KEEP_FOR_POTENTIAL_FUTURE_USE
#if NO
    internal class Util
    {
        private static void _Format(System.Text.StringBuilder sb1,
                                    byte[] b,
                                    int offset,
                                    int length)
        {

            System.Text.StringBuilder sb2 = new System.Text.StringBuilder();
            sb1.Append("0000    ");
            int i;
            for (i = 0; i < length; i++)
            {
                int x = offset+i;
                if (i != 0 && i % 16 == 0)
                {
                    sb1.Append("    ")
                        .Append(sb2)
                        .Append("\n")
                        .Append(String.Format("{0:X4}    ", i));
                    sb2.Remove(0,sb2.Length);
                }
                sb1.Append(System.String.Format("{0:X2} ", b[x]));
                if (b[x] >=32 && b[x] <= 126)
                    sb2.Append((char)b[x]);
                else
                    sb2.Append(".");
            }
            if (sb2.Length > 0)
            {
                sb1.Append(new String(' ', ((16 - i%16) * 3) + 4))
                    .Append(sb2);
            }
        }



        internal static string FormatByteArray(byte[] b, int limit)
        {
            System.Text.StringBuilder sb1 = new System.Text.StringBuilder();

            if ((limit * 2 > b.Length) || limit == 0)
            {
                _Format(sb1, b, 0, b.Length);
            }
            else
            {
                // first N bytes of the buffer
                _Format(sb1, b, 0, limit);

                if (b.Length > limit * 2)
                    sb1.Append(String.Format("\n   ...({0} other bytes here)....\n", b.Length - limit * 2));

                // last N bytes of the buffer
                _Format(sb1, b, b.Length - limit, limit);
            }

            return sb1.ToString();
        }


        internal static string FormatByteArray(byte[] b)
        {
            return FormatByteArray(b, 0);
        }
    }

#endif
	#endregion




	/// <summary>
	///   A stream that encrypts as it writes, or decrypts as it reads.  The
	///   Crypto is AES in CTR (counter) mode, which is compatible with the AES
	///   encryption employed by WinZip 12.0.
	/// </summary>
	/// <remarks>
	///   <para>
	///     The AES/CTR encryption protocol used by WinZip works like this:
	///
	///       - start with a counter, initialized to zero.
	///
	///       - to encrypt, take the data by 16-byte blocks. For each block:
	///         - apply the transform to the counter
	///         - increement the counter
	///         - XOR the result of the transform with the plaintext to
	///           get the ciphertext.
	///         - compute the mac on the encrypted bytes
	///       - when finished with all blocks, store the computed MAC.
	///
	///       - to decrypt, take the data by 16-byte blocks. For each block:
	///         - compute the mac on the encrypted bytes,
	///         - apply the transform to the counter
	///         - increement the counter
	///         - XOR the result of the transform with the ciphertext to
	///           get the plaintext.
	///       - when finished with all blocks, compare the computed MAC against
	///         the stored MAC
	///
	///   </para>
	/// </remarks>
	//
	internal class WinZipAesCipherStream : Stream
	{
		private WinZipAesCrypto _params;
		private System.IO.Stream _s;
		private CryptoMode _mode;
		private int _nonce;
		private bool _finalBlock;

		internal HMACSHA1 _mac;

		// Use RijndaelManaged from .NET 2.0.
		// AesManaged came in .NET 3.5, but we want to limit
		// dependency to .NET 2.0.  AES is just a restricted form
		// of Rijndael (fixed block size of 128, some crypto modes not supported).

		internal RijndaelManaged _aesCipher;
		internal ICryptoTransform _xform;

		private const int BLOCK_SIZE_IN_BYTES = 16;

		private byte[] counter = new byte[BLOCK_SIZE_IN_BYTES];
		private byte[] counterOut = new byte[BLOCK_SIZE_IN_BYTES];

		// I've had a problem when wrapping a WinZipAesCipherStream inside
		// a DeflateStream. Calling Read() on the DeflateStream results in
		// a Read() on the WinZipAesCipherStream, but the buffer is larger
		// than the total size of the encrypted data, and larger than the
		// initial Read() on the DeflateStream!  When the encrypted
		// bytestream is embedded within a larger stream (As in a zip
		// archive), the Read() doesn't fail with EOF.  This causes bad
		// data to be returned, and it messes up the MAC.

		// This field is used to provide a hard-stop to the size of
		// data that can be read from the stream.  In Read(), if the buffer or
		// read request goes beyond the stop, we truncate it.

		private long _length;
		private long _totalBytesXferred;
		private byte[] _PendingWriteBlock;
		private int _pendingCount;
		private byte[] _iobuf;

		/// <summary>
		/// The constructor.
		/// </summary>
		/// <param name="s">The underlying stream</param>
		/// <param name="mode">To either encrypt or decrypt.</param>
		/// <param name="cryptoParams">The pre-initialized WinZipAesCrypto object.</param>
		/// <param name="length">The maximum number of bytes to read from the stream.</param>
		internal WinZipAesCipherStream(System.IO.Stream s, WinZipAesCrypto cryptoParams, long length, CryptoMode mode)
			: this(s, cryptoParams, mode)
		{
			// don't read beyond this limit!
			_length = length;
			//Console.WriteLine("max length of AES stream: {0}", _length);
		}


#if WANT_TRACE
            Stream untransformed;
        String traceFileUntransformed;
        Stream transformed;
        String traceFileTransformed;
#endif


		internal WinZipAesCipherStream(System.IO.Stream s, WinZipAesCrypto cryptoParams, CryptoMode mode)
			: base()
		{
			TraceOutput("-------------------------------------------------------");
			TraceOutput("Create {0:X8}", this.GetHashCode());

			_params = cryptoParams;
			_s = s;
			_mode = mode;
			_nonce = 1;

			if (_params == null)
				throw new BadPasswordException("Supply a password to use AES encryption.");

			int keySizeInBits = _params.KeyBytes.Length * 8;
			if (keySizeInBits != 256 && keySizeInBits != 128 && keySizeInBits != 192)
				throw new ArgumentOutOfRangeException("keysize",
													  "size of key must be 128, 192, or 256");

			_mac = new HMACSHA1(_params.MacIv);

			_aesCipher = new System.Security.Cryptography.RijndaelManaged();
			_aesCipher.BlockSize = 128;
			_aesCipher.KeySize = keySizeInBits;  // 128, 192, 256
			_aesCipher.Mode = CipherMode.ECB;
			_aesCipher.Padding = PaddingMode.None;

			byte[] iv = new byte[BLOCK_SIZE_IN_BYTES]; // all zeroes

			// Create an ENCRYPTOR, regardless whether doing decryption or encryption.
			// It is reflexive.
			_xform = _aesCipher.CreateEncryptor(_params.KeyBytes, iv);

			if (_mode == CryptoMode.Encrypt)
			{
				_iobuf = new byte[2048];
				_PendingWriteBlock = new byte[BLOCK_SIZE_IN_BYTES];
			}


#if WANT_TRACE
                traceFileUntransformed = "unpack\\WinZipAesCipherStream.trace.untransformed.out";
            traceFileTransformed = "unpack\\WinZipAesCipherStream.trace.transformed.out";

            untransformed = System.IO.File.Create(traceFileUntransformed);
            transformed = System.IO.File.Create(traceFileTransformed);
#endif
		}

		private void XorInPlace(byte[] buffer, int offset, int count)
		{
			for (int i = 0; i < count; i++)
			{
				buffer[offset + i] = (byte)(counterOut[i] ^ buffer[offset + i]);
			}
		}

		private void WriteTransformOneBlock(byte[] buffer, int offset)
		{
			System.Array.Copy(BitConverter.GetBytes(_nonce++), 0, counter, 0, 4);
			_xform.TransformBlock(counter,
								  0,
								  BLOCK_SIZE_IN_BYTES,
								  counterOut,
								  0);
			XorInPlace(buffer, offset, BLOCK_SIZE_IN_BYTES);
			_mac.TransformBlock(buffer, offset, BLOCK_SIZE_IN_BYTES, null, 0);
		}


		private void WriteTransformBlocks(byte[] buffer, int offset, int count)
		{
			int posn = offset;
			int last = count + offset;

			while (posn < buffer.Length && posn < last)
			{
				WriteTransformOneBlock(buffer, posn);
				posn += BLOCK_SIZE_IN_BYTES;
			}
		}


		private void WriteTransformFinalBlock()
		{
			if (_pendingCount == 0)
				throw new InvalidOperationException("No bytes available.");

			if (_finalBlock)
				throw new InvalidOperationException("The final block has already been transformed.");

			System.Array.Copy(BitConverter.GetBytes(_nonce++), 0, counter, 0, 4);
			counterOut = _xform.TransformFinalBlock(counter,
													0,
													BLOCK_SIZE_IN_BYTES);
			XorInPlace(_PendingWriteBlock, 0, _pendingCount);
			_mac.TransformFinalBlock(_PendingWriteBlock, 0, _pendingCount);
			_finalBlock = true;
		}





		private int ReadTransformOneBlock(byte[] buffer, int offset, int last)
		{
			if (_finalBlock)
				throw new NotSupportedException();

			int bytesRemaining = last - offset;
			int bytesToRead = (bytesRemaining > BLOCK_SIZE_IN_BYTES)
				? BLOCK_SIZE_IN_BYTES
				: bytesRemaining;

			// update the counter
			System.Array.Copy(BitConverter.GetBytes(_nonce++), 0, counter, 0, 4);

			// Determine if this is the final block
			if ((bytesToRead == bytesRemaining) &&
				(_length > 0) &&
				(_totalBytesXferred + last == _length))
			{
				_mac.TransformFinalBlock(buffer, offset, bytesToRead);
				counterOut = _xform.TransformFinalBlock(counter,
														0,
														BLOCK_SIZE_IN_BYTES);
				_finalBlock = true;
			}
			else
			{
				_mac.TransformBlock(buffer, offset, bytesToRead, null, 0);
				_xform.TransformBlock(counter,
									  0, // offset
									  BLOCK_SIZE_IN_BYTES,
									  counterOut,
									  0);  // offset
			}

			XorInPlace(buffer, offset, bytesToRead);
			return bytesToRead;
		}



		private void ReadTransformBlocks(byte[] buffer, int offset, int count)
		{
			int posn = offset;
			int last = count + offset;

			while (posn < buffer.Length && posn < last)
			{
				int n = ReadTransformOneBlock(buffer, posn, last);
				posn += n;
			}
		}



		public override int Read(byte[] buffer, int offset, int count)
		{
			if (_mode == CryptoMode.Encrypt)
				throw new NotSupportedException();

			if (buffer == null)
				throw new ArgumentNullException("buffer");

			if (offset < 0)
				throw new ArgumentOutOfRangeException("offset",
													  "Must not be less than zero.");
			if (count < 0)
				throw new ArgumentOutOfRangeException("count",
													  "Must not be less than zero.");

			if (buffer.Length < offset + count)
				throw new ArgumentException("The buffer is too small");

			// When I wrap a WinZipAesStream in a DeflateStream, the
			// DeflateStream asks its captive to read 4k blocks, even if the
			// encrypted bytestream is smaller than that.  This is a way to
			// limit the number of bytes read.

			int bytesToRead = count;

			if (_totalBytesXferred >= _length)
			{
				return 0; // EOF
			}

			long bytesRemaining = _length - _totalBytesXferred;
			if (bytesRemaining < count) bytesToRead = (int)bytesRemaining;

			int n = _s.Read(buffer, offset, bytesToRead);


#if WANT_TRACE
                untransformed.Write(buffer, offset, bytesToRead);
#endif

			ReadTransformBlocks(buffer, offset, bytesToRead);

#if WANT_TRACE
                transformed.Write(buffer, offset, bytesToRead);
#endif
			_totalBytesXferred += n;
			return n;
		}



		/// <summary>
		/// Returns the final HMAC-SHA1-80 for the data that was encrypted.
		/// </summary>
		public byte[] FinalAuthentication
		{
			get
			{
				if (!_finalBlock)
				{
					// special-case zero-byte files
					if (_totalBytesXferred != 0)
						throw new BadStateException("The final hash has not been computed.");

					// Must call ComputeHash on an empty byte array when no data
					// has run through the MAC.

					byte[] b = { };
					_mac.ComputeHash(b);
					// fall through
				}
				byte[] macBytes10 = new byte[10];
				System.Array.Copy(_mac.Hash, 0, macBytes10, 0, 10);
				return macBytes10;
			}
		}


		public override void Write(byte[] buffer, int offset, int count)
		{
			if (_finalBlock)
				throw new InvalidOperationException("The final block has already been transformed.");

			if (_mode == CryptoMode.Decrypt)
				throw new NotSupportedException();

			if (buffer == null)
				throw new ArgumentNullException("buffer");

			if (offset < 0)
				throw new ArgumentOutOfRangeException("offset",
													  "Must not be less than zero.");
			if (count < 0)
				throw new ArgumentOutOfRangeException("count",
													  "Must not be less than zero.");
			if (buffer.Length < offset + count)
				throw new ArgumentException("The offset and count are too large");

			if (count == 0)
				return;

			TraceOutput("Write off({0}) count({1})", offset, count);

#if WANT_TRACE
            untransformed.Write(buffer, offset, count);
#endif

			// For proper AES encryption, an AES encryptor application calls
			// TransformBlock repeatedly for all 16-byte blocks except the
			// last. For the last block, it then calls TransformFinalBlock().
			//
			// This class is a stream that encrypts via Write().  But, it's not
			// possible to recognize which are the "last" bytes from within the call
			// to Write(). The caller can call Write() several times in succession,
			// with varying buffers. This class only "knows" that the last bytes
			// have been written when the app calls Close().
			//
			// Therefore, this class buffers writes: After completion every Write(),
			// a 16-byte "pending" block (_PendingWriteBlock) must hold between 1
			// and 16 bytes, which will be used in TransformFinalBlock if the app
			// calls Close() immediately thereafter. Also, every write must
			// transform any pending bytes, before transforming the data passed in
			// to the current call.
			//
			// In operation, after the first call to Write() and before the call to
			// Close(), one full or partial block of bytes is always available,
			// pending.  At time of Close(), this class calls
			// WriteTransformFinalBlock() to flush the pending bytes.
			//
			// This approach works whether the caller writes in odd-sized batches,
			// for example 5000 bytes, or in batches that are neat multiples of the
			// blocksize (16).
			//
			// Logicaly, what we do is this:
			//
			//  1. if there are fewer than 16 bytes (pending + current), then
			//     just copy them into th pending buffer and return.
			//
			//  2. there are more than 16 bytes to write. So, take the leading slice
			//     of bytes from the current buffer, enough to fill the pending
			//     buffer. Transform the pending block, and write it out.
			//
			//  3. Take the trailing slice of bytes (a full block or a partial block),
			//     and copy it to the pending block for next time.
			//
			//  4. transform and write all the other blocks, the middle slice.
			//

			// There are 16 or fewer bytes, so just buffer the bytes.
			if (count + _pendingCount <= BLOCK_SIZE_IN_BYTES)
			{
				Buffer.BlockCopy(buffer,
								 offset,
								 _PendingWriteBlock,
								 _pendingCount,
								 count);
				_pendingCount += count;

				// At this point, _PendingWriteBlock contains up to
				// BLOCK_SIZE_IN_BYTES bytes, and _pendingCount ranges from 0 to
				// BLOCK_SIZE_IN_BYTES. We don't want to xform+write them yet,
				// because this may have been the last block.  The last block gets
				// written at Close().
				return;
			}

			// We know there are at least 17 bytes, counting those in the current
			// buffer, along with the (possibly empty) pending block.

			int bytesRemaining = count;
			int curOffset = offset;

			// workitem 12815
			//
			// xform chunkwise ... Cannot transform in place using the original
			// buffer because that is user-maintained.

			if (_pendingCount != 0)
			{
				// We have more than one block of data to write, therefore it is safe
				// to xform+write.
				int fillCount = BLOCK_SIZE_IN_BYTES - _pendingCount;

				// fillCount is possibly zero here. That happens when the pending
				// buffer held 16 bytes (one complete block) before this call to
				// Write.
				if (fillCount > 0)
				{
					Buffer.BlockCopy(buffer,
									 offset,
									 _PendingWriteBlock,
									 _pendingCount,
									 fillCount);

					// adjust counts:
					bytesRemaining -= fillCount;
					curOffset += fillCount;
				}

				// xform and write:
				WriteTransformOneBlock(_PendingWriteBlock, 0);
				_s.Write(_PendingWriteBlock, 0, BLOCK_SIZE_IN_BYTES);
				_totalBytesXferred += BLOCK_SIZE_IN_BYTES;
				_pendingCount = 0;
			}

			// At this point _PendingWriteBlock is empty, and bytesRemaining is
			// always greater than 0.

			// Now, xform N blocks, where N = floor((bytesRemaining-1)/16).  If
			// writing 32 bytes, then xform 1 block, and stage the remaining 16.  If
			// writing 10037 bytes, xform 627 blocks of 16 bytes, then stage the
			// remaining 5 bytes.

			int blocksToXform = (bytesRemaining - 1) / BLOCK_SIZE_IN_BYTES;
			_pendingCount = bytesRemaining - (blocksToXform * BLOCK_SIZE_IN_BYTES);

			// _pendingCount is ALWAYS between 1 and 16.
			// Put the last _pendingCount bytes into the pending block.
			Buffer.BlockCopy(buffer,
							 curOffset + bytesRemaining - _pendingCount,
							 _PendingWriteBlock,
							 0,
							 _pendingCount);
			bytesRemaining -= _pendingCount;
			_totalBytesXferred += bytesRemaining; // will be true after the loop

			// now, transform all the full blocks preceding that.
			// bytesRemaining is always a multiple of 16 .
			if (blocksToXform > 0)
			{
				do
				{
					int c = _iobuf.Length;
					if (c > bytesRemaining) c = bytesRemaining;
					Buffer.BlockCopy(buffer,
									 curOffset,
									 _iobuf,
									 0,
									 c);

					WriteTransformBlocks(_iobuf, 0, c);
					_s.Write(_iobuf, 0, c);
					bytesRemaining -= c;
					curOffset += c;
				} while (bytesRemaining > 0);
			}
		}



		/// <summary>
		///   Close the stream.
		/// </summary>
		public override void Close()
		{
			TraceOutput("Close {0:X8}", this.GetHashCode());

			// In the degenerate case, no bytes have been written to the
			// stream at all.  Need to check here, and NOT emit the
			// final block if Write has not been called.
			if (_pendingCount > 0)
			{
				WriteTransformFinalBlock();
				_s.Write(_PendingWriteBlock, 0, _pendingCount);
				_totalBytesXferred += _pendingCount;
				_pendingCount = 0;
			}
			_s.Close();

#if WANT_TRACE
            untransformed.Close();
            transformed.Close();
            Console.WriteLine("\nuntransformed bytestream is in  {0}", traceFileUntransformed);
            Console.WriteLine("\ntransformed bytestream is in  {0}", traceFileTransformed);
#endif
			TraceOutput("-------------------------------------------------------");
		}


		/// <summary>
		/// Returns true if the stream can be read.
		/// </summary>
		public override bool CanRead
		{
			get
			{
				if (_mode != CryptoMode.Decrypt) return false;
				return true;
			}
		}


		/// <summary>
		/// Always returns false.
		/// </summary>
		public override bool CanSeek
		{
			get { return false; }
		}

		/// <summary>
		/// Returns true if the CryptoMode is Encrypt.
		/// </summary>
		public override bool CanWrite
		{
			get { return (_mode == CryptoMode.Encrypt); }
		}

		/// <summary>
		/// Flush the content in the stream.
		/// </summary>
		public override void Flush()
		{
			_s.Flush();
		}

		/// <summary>
		/// Getting this property throws a NotImplementedException.
		/// </summary>
		public override long Length
		{
			get { throw new NotImplementedException(); }
		}

		/// <summary>
		/// Getting or Setting this property throws a NotImplementedException.
		/// </summary>
		public override long Position
		{
			get { throw new NotImplementedException(); }
			set { throw new NotImplementedException(); }
		}

		/// <summary>
		/// This method throws a NotImplementedException.
		/// </summary>
		public override long Seek(long offset, System.IO.SeekOrigin origin)
		{
			throw new NotImplementedException();
		}

		/// <summary>
		/// This method throws a NotImplementedException.
		/// </summary>
		public override void SetLength(long value)
		{
			throw new NotImplementedException();
		}



		[System.Diagnostics.ConditionalAttribute("Trace")]
		private void TraceOutput(string format, params object[] varParams)
		{
			if (!Monitor.TryEnter(_outputLock, Fiber.LockTimeout))
				Fiber.ThrowLockTimeoutException(_outputLock);
			try
			{
				int tid = System.Threading.Thread.CurrentThread.GetHashCode();
				Console.ForegroundColor = (ConsoleColor)(tid % 8 + 8);
				Console.Write("{0:000} WZACS ", tid);
				Console.WriteLine(format, varParams);
				Console.ResetColor();
			}
			finally
			{
				Monitor.Exit(_outputLock);
			}
		}

		private object _outputLock = new Object();
	}
#endif
}
